/*
 * VisualTransferHandler.java 05 june 2009
 *
 * Sweet Home 3D, Copyright (c) 2009 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.swing;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceContext;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.InvalidDnDOperationException;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.TransferHandler;

import com.eteks.sweethome3d.tools.OperatingSystem;

/**
 * Transfer handler with visual representation on systems that support it.
 * @author Emmanuel Puybaret
 */
public class VisualTransferHandler extends TransferHandler
{
	private final DragGestureRecognizerWithVisualRepresentation dragGestureRecognizerWithVisualRepresentation = new DragGestureRecognizerWithVisualRepresentation();
	
	/**
	 * Causes the Swing drag support to be initiated with a drag icon if necessary. 
	 * This method implements the expected behavior of <code>exportAsDrag</code> 
	 * if <code>getVisualRepresentation</code> was used in <code>TransferHandler</code> implementation.  
	 * As only Mac OS X supports drag image, the drag icon will actually appear only on this system.
	 */
	public void exportAsDrag(JComponent source, InputEvent ev, int action)
	{
		int sourceActions = getSourceActions(source);
		int dragAction = sourceActions & action;
		if (DragSource.isDragImageSupported() && dragAction != NONE && (ev instanceof MouseEvent))
		{
			this.dragGestureRecognizerWithVisualRepresentation.gestured(source, (MouseEvent) ev, sourceActions,
					dragAction);
		}
		else
		{
			super.exportAsDrag(source, ev, action);
		}
	}
	
	/**
	 * A drag gesture recognizer that uses transfer handler visual representation.
	 * See <code>TransferHandler$SwingDragGestureRecognizer</code> for original code. 
	 */
	private class DragGestureRecognizerWithVisualRepresentation extends DragGestureRecognizer
	{
		public DragGestureRecognizerWithVisualRepresentation()
		{
			super(DragSource.getDefaultDragSource(), null, NONE, new DragListenerWithVisualRepresentation());
		}
		
		void gestured(JComponent component, MouseEvent ev, int sourceActions, int action)
		{
			setComponent(component);
			setSourceActions(sourceActions);
			appendEvent(ev);
			fireDragGestureRecognized(action, ev.getPoint());
		}
		
		@Override
		protected void registerListeners()
		{}
		
		@Override
		protected void unregisterListeners()
		{}
	};
	
	/**
	 * A drag listener that uses transfer handler visual representation for drag image.
	 * See <code>TransferHandler$DragHandler</code> for base code. 
	 */
	private class DragListenerWithVisualRepresentation implements DragGestureListener, DragSourceListener
	{
		private boolean autoscrolls;
		private final Image EMPTY_IMAGE = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
		// Set dragged image offset which is strangely very different from an OS to the other 
		private final Point OFFSET = OperatingSystem.isMacOSX()
				? (OperatingSystem.isJavaVersionBetween("1.7", "1.7.0_40") ? new Point(12, -120) : new Point(12, 24))
				: new Point(-12, -24);
		
		public void dragGestureRecognized(DragGestureEvent ev)
		{
			JComponent component = (JComponent) ev.getComponent();
			Transferable transferable = createTransferable(component);
			if (transferable != null)
			{
				this.autoscrolls = component.getAutoscrolls();
				component.setAutoscrolls(false);
				try
				{
					Icon icon = getVisualRepresentation(transferable);
					if (icon != null)
					{
						// Create a half transparent drag image from icon
						BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(),
								BufferedImage.TYPE_INT_ARGB);
						Graphics2D g2D = (Graphics2D) image.getGraphics();
						g2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.66f));
						icon.paintIcon(component, g2D, 0, 0);
						g2D.dispose();
						
						ev.startDrag(null, image, OFFSET, transferable, this);
					}
					else
					{
						// Force the use of an empty image otherwise Mac OS X uses a grey rectangle
						ev.startDrag(null, EMPTY_IMAGE, new Point(48, 48), transferable, this);
					}
				}
				catch (InvalidDnDOperationException re)
				{
					component.setAutoscrolls(this.autoscrolls);
				}
			}
			
			exportDone(component, transferable, NONE);
		}
		
		public void dragEnter(DragSourceDragEvent ev)
		{}
		
		public void dragOver(DragSourceDragEvent ev)
		{}
		
		public void dragExit(DragSourceEvent ev)
		{}
		
		public void dragDropEnd(DragSourceDropEvent ev)
		{
			DragSourceContext dragSourceContext = ev.getDragSourceContext();
			JComponent component = (JComponent) dragSourceContext.getComponent();
			if (ev.getDropSuccess())
			{
				exportDone(component, dragSourceContext.getTransferable(), ev.getDropAction());
			}
			else
			{
				exportDone(component, dragSourceContext.getTransferable(), NONE);
			}
			component.setAutoscrolls(this.autoscrolls);
		}
		
		public void dropActionChanged(DragSourceDragEvent ev)
		{}
	}
}
